Add Interactive Brokers Provider#1722
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds Interactive Brokers (IBKR) provider: DB migrations, IbkrItem/IbkrAccount models, Provider::IbkrFlex client and Flex XML parser, processors to import holdings/trades/cash and upsert historical balances, account syncer hooks, controllers/routes/UI for setup/linking, exchange-rate and activity-security plumbing, tests/fixtures, and a Nix dev shell. ChangesIBKR Provider Integration
Sequence DiagramsequenceDiagram
participant User
participant App as Rails App
participant IBKR as Provider::IbkrFlex
participant DB as Database
User->>App: Configure IBKR / trigger sync
App->>IBKR: SendRequest -> poll GetStatement
IBKR-->>App: FlexQueryResponse XML
App->>App: ReportParser.parse → IbkrItem::Importer upsert accounts
App->>App: Run HoldingsProcessor / ActivitiesProcessor / HistoricalBalancesSync
App->>DB: Upsert holdings, trades, transactions, balances
App->>User: UI shows linked accounts / setup completion
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a382d689c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/models/holding/materializer.rb (1)
145-154:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftAdd test coverage for provider snapshot cleanup and reconsider broad deletion approach.
The logic assumes provider snapshots are complete (containing all securities for a date), but this assumption is not validated by tests. If a provider sends partial data (e.g., only AAPL holdings), the code will delete calculated GOOGL holdings for that date, causing data loss.
The safer approach already exists in
cleanup_shadowed_calculated_holdings(lines 124-143), which uses a targeted subquery to delete only calculated holdings that have an exact match with a provider holding on the same (security_id, date, currency). Consider either:
- Add comprehensive test coverage that validates provider snapshot completeness and demonstrates the deletion is safe.
- Refactor to use targeted deletion like
cleanup_shadowed_calculated_holdings, matching on (security_id, date, currency) instead of deleting all calculated holdings on the date.The broad deletion at lines 149-151 is inconsistent with the safer, targeted approach used just above it and creates unnecessary risk of data loss for partial provider snapshots.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/models/holding/materializer.rb` around lines 145 - 154, The cleanup_stale_calculated_rows_on_latest_provider_snapshot method currently deletes all calculated holdings for a provider_snapshot_date, which risks removing valid calculated rows when provider snapshots are partial; either add tests that validate provider snapshot completeness for account.latest_provider_holdings_snapshot_date and assert that deleting all account.holdings.where(account_provider_id: nil, date: provider_snapshot_date) is safe, or refactor this method to follow the targeted approach used in cleanup_shadowed_calculated_holdings: construct a subquery/join between account.holdings (where account_provider_id is nil) and provider holdings for the same account and provider_snapshot_date and delete only calculated rows that match on security_id, date and currency (and account/provider identifiers) instead of deleting by date alone.
🧹 Nitpick comments (7)
app/models/holding.rb (1)
259-265: ⚡ Quick winConsider logging conversion failures for observability.
The
Money::ConversionErrorrescue silently falls back to the unconvertedamount, which could produce incorrect weight calculations when exchange rates are missing. This silent failure makes it difficult to detect missing exchange rate data.📊 Proposed enhancement to add debug logging
def amount_in_account_currency return amount if currency == account.currency Money.new(amount, currency).exchange_to(account.currency, date: date).amount rescue Money::ConversionError + Rails.logger.debug( + "Holding#amount_in_account_currency - Missing exchange rate for #{currency}→#{account.currency} " \ + "on #{date}, holding_id=#{id}" + ) amount end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/models/holding.rb` around lines 259 - 265, The rescue in amount_in_account_currency currently swallows Money::ConversionError; update it to log the conversion failure (including source currency, target account.currency, date, and the error message/exception) before returning amount so missing exchange-rate incidents are observable; reference the amount_in_account_currency method and the Money::ConversionError rescue block and use the model logger (e.g., Rails.logger or logger) to emit a warn or error-level message with enough context to trace the failed conversion.app/models/ibkr_item/unlinking.rb (1)
24-24: ⚡ Quick winConsider batch deletion for better performance.
Line 24 destroys links individually in a loop, which can be slow when many links exist. If
AccountProvidercallbacks aren't required during unlink, consider usingdestroy_allfor better performance.⚡ Proposed optimization
ActiveRecord::Base.transaction do Holding.where(account_provider_id: link_ids).update_all(account_provider_id: nil) if link_ids.any? - links.each(&:destroy!) + AccountProvider.where(id: link_ids).destroy_all endNote: Only apply this if
AccountProvider#destroycallbacks aren't needed during unlink. If callbacks are required (e.g., for cleanup), keep the current implementation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/models/ibkr_item/unlinking.rb` at line 24, The current unlink loop uses links.each(&:destroy!) which deletes each Link record individually and can be slow; if AccountProvider#destroy callbacks are not required during unlink, replace the per-record destroy with a batched deletion (e.g., use destroy_all or delete_all on the links relation) to improve performance; locate the call to links.each(&:destroy!) in unlinking.rb (the unlink flow that pertains to AccountProvider) and switch to links.destroy_all (or links.delete_all if callbacks and validations must be skipped) ensuring you pick destroy_all only if callbacks are safe to omit and AccountProvider#destroy is not needed.test/models/snaptrade_account_processor_test.rb (1)
134-134: ⚡ Quick winAvoid stubbing
set_current_balancein this behavior test.Line 134 couples this test to internals while the test already asserts output state (
balance,cash_balance,currency). Dropping the stub will keep the test focused on behavior and less brittle.Suggested change
- Account.any_instance.stubs(:set_current_balance)As per coding guidelines: “Distinguish between commands and query methods. Test output of query methods … Never test the implementation details of one class in another class's test suite.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/models/snaptrade_account_processor_test.rb` at line 134, Remove the fragile stub of Account.any_instance.stubs(:set_current_balance) so the test exercises the real Account#set_current_balance behavior and only asserts observable outputs (balance, cash_balance, currency); delete that stub line and, if the real method requires setup, initialize the Account fixture or its dependencies (e.g., balances, transactions) instead or, if necessary, stub a lower-level external dependency used by Account#set_current_balance rather than stubbing the method under test.app/views/ibkr_items/select_existing_account.html.erb (1)
24-24: ⚡ Quick winMove currency formatting to the server side.
The view constructs a
Money::Currencyobject and formats the balance inline. Per coding guidelines, currencies should be formatted server-side and passed to views for display only. This logic should move to the controller or a helper method.🔄 Suggested refactor
In the controller, preformat the balance:
`@available_ibkr_accounts` = `@available_ibkr_accounts.map` do |acct| OpenStruct.new( id: acct.id, name: acct.name, ibkr_account_id: acct.ibkr_account_id, currency: acct.currency, formatted_balance: number_to_currency( acct.current_balance || 0, unit: Money::Currency.new(acct.currency || "USD").symbol ) ) endThen in the view:
-<%= ibkr_account.currency %> • Balance: <%= number_to_currency((ibkr_account.current_balance || 0), unit: Money::Currency.new(ibkr_account.currency || "USD").symbol) %> +<%= ibkr_account.currency %> • Balance: <%= ibkr_account.formatted_balance %>As per coding guidelines: "Format currencies, numbers, dates, and other values server-side, then pass to Stimulus controllers for display only".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/views/ibkr_items/select_existing_account.html.erb` at line 24, The view is formatting currency inline (using number_to_currency and Money::Currency.new on ibkr_account.current_balance); move this logic to the server by adding a formatted_balance attribute for each account in the controller (e.g., when preparing `@available_ibkr_accounts`) or by creating a helper method (e.g., format_currency_balance(account) used from the controller) that computes number_to_currency(acct.current_balance || 0, unit: Money::Currency.new(acct.currency || "USD").symbol) and pass that formatted_balance to the view so the template only renders ibkr_account.formatted_balance.app/views/settings/providers/_ibkr_panel.html.erb (1)
11-53: ⚡ Quick winFix invalid nested list markup in setup instructions.
<ul>elements are currently direct children of<ol>. They should be nested inside the parent<li>to keep semantic structure valid.Suggested structure adjustment
- <ol> - <li>...enable the following sections:</li> - <ul class="list-disc list-inside mb-2"> - <li>Account Information: ...</li> - ... - </ul> + <ol> + <li> + ...enable the following sections: + <ul class="list-disc list-inside mb-2"> + <li>Account Information: ...</li> + ... + </ul> + </li>As per coding guidelines:
{app/views/**,app/helpers/**}: “Always generate semantic HTML.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/views/settings/providers/_ibkr_panel.html.erb` around lines 11 - 53, The ordered list (<ol>) in _ibkr_panel.html.erb has multiple <ul> elements as direct children which breaks semantic nesting; move each <ul> so it is nested inside the preceding related <li> (e.g., wrap the "Cash Report", "Cash Transactions", "Net Asset Value", "Open Positions", "Trades" and the "Set the following configuration options" lists inside their parent <li> elements) so every <ul> is a child of its corresponding <li> and the <ol> contains only <li> children.app/views/ibkr_items/_ibkr_item.html.erb (1)
11-13: ⚡ Quick winReplace raw hex color classes with design-system tokens.
The IB badge uses
bg-[#D32F2F]/10andtext-[#D32F2F], which breaks token consistency.Suggested token-based tweak
- <div class="flex items-center justify-center h-8 w-8 rounded-full bg-[`#D32F2F`]/10"> - <span class="text-[`#D32F2F`] text-xs font-medium">IB</span> + <div class="flex items-center justify-center h-8 w-8 rounded-full bg-destructive/10"> + <span class="text-destructive text-xs font-medium">IB</span> </div>As per coding guidelines:
app/**/*.{erb,css,html}: “Use Tailwind CSS design tokens… instead of raw Tailwind classes.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/views/ibkr_items/_ibkr_item.html.erb` around lines 11 - 13, Replace the raw hex Tailwind classes on the IB badge with design-system token classes: swap the div's bg-[`#D32F2F`]/10 for the appropriate background token (e.g., bg-destructive-10 or the project's equivalent token) and replace the span's text-[`#D32F2F`] with the matching text token (e.g., text-destructive-600 or the project's equivalent); update the element with those token class names (the div with the rounded bg and the span with "IB") so the component uses the shared Tailwind design tokens instead of hard-coded hex values.app/models/provider/ibkr_flex.rb (1)
19-21: ⚡ Quick winExternalize the Flex endpoint and timeout instead of hard-coding them.
These are provider configuration values, so keeping them env-backed will make sandboxing, testing, and operational overrides easier without another code change.
Proposed refactor
- base_uri "https://ndcdyn.interactivebrokers.com/AccountManagement/FlexWebService" - headers "User-Agent" => "Sure Finance IBKR Flex Client" - default_options.merge!({ timeout: 120 }.merge(httparty_ssl_options)) + base_uri ENV.fetch("IBKR_FLEX_BASE_URL", "https://ndcdyn.interactivebrokers.com/AccountManagement/FlexWebService") + headers "User-Agent" => ENV.fetch("IBKR_FLEX_USER_AGENT", "Sure Finance IBKR Flex Client") + default_options.merge!({ + timeout: ENV.fetch("IBKR_FLEX_TIMEOUT", 120).to_i + }.merge(httparty_ssl_options))As per coding guidelines: use environment variables instead of hard-coded values in configuration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/models/provider/ibkr_flex.rb` around lines 19 - 21, Replace hard-coded Flex endpoint and timeout with env-backed config: read endpoint from an environment variable (e.g. ENV['IBKR_FLEX_ENDPOINT'] with a sensible default like the current URL) and read timeout from an environment variable (e.g. ENV['IBKR_FLEX_TIMEOUT'] parsed to integer with default 120). Use those variables when calling base_uri and when merging into default_options (the existing default_options.merge! call) while keeping the existing headers and httparty_ssl_options. Update any relevant initialization or docs to note the new ENV keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/controllers/ibkr_items_controller.rb`:
- Around line 48-49: The redirect calls using redirect_to
settings_providers_path with status: :unprocessable_entity should use a 3xx
redirect status instead of 422; change the status option to a redirect status
(e.g. :see_other or :found) on the redirect_to calls (the ones referencing
settings_providers_path and `@error_message`) so both occurrences that currently
pass status: :unprocessable_entity use a 3xx symbol (prefer :see_other for
POST/Turbo compatibility).
- Around line 185-188: The rescue block in ibkr_items_controller.rb currently
logs the full exception but passes e.message to the end-user via redirect_to
settings_providers_path, alert: t(".failed", message: e.message), which can leak
internals; keep the Rails.logger.error(...) as-is (or enhance it with
e.full_message/backtrace) but change the redirect to use a generic translated
message (e.g. redirect_to settings_providers_path, alert: t(".failed") or
t(".failed_generic")) and remove the message: e.message interpolation; also
update the corresponding translation key (".failed") so it does not expect a
message param.
In `@app/models/account/provider_import_adapter.rb`:
- Around line 594-595: The export currently always assigns exchange_rate in the
attributes hash (exchange_rate) which causes persisted FX rates to be cleared
when the provider omits the field; change the builder in
provider_import_adapter.rb so you only add the exchange_rate attribute when the
incoming value is present (e.g., skip or merge the key when exchange_rate is
nil/blank) instead of unconditionally setting exchange_rate alongside
investment_activity_label, ensuring re-imports don't overwrite an existing
persisted rate.
In `@app/models/family/ibkr_connectable.rb`:
- Around line 12-15: In create_ibkr_item! ensure blank strings don't bypass the
default: replace the current item_name || "Interactive Brokers" usage in the
ibkr_items.create! call with a presence-aware check (e.g. use item_name.presence
or equivalent) so that "" will fall back to "Interactive Brokers" when building
the record in create_ibkr_item!.
In `@app/models/ibkr_account/activities_processor.rb`:
- Around line 150-163: The supported_trade? predicate is rejecting
commission-free trades by requiring row[:ib_commission] and
row[:ib_commission_currency]; remove those two presence checks from
supported_trade? so trades without commission values still pass validation
(leave fx_rate_available?(row) and all other required checks intact), since
import_commission_transaction already handles nil/zero commission values. Ensure
no other logic relies on those fields being present at this validation point.
In `@app/models/ibkr_account/data_helpers.rb`:
- Around line 58-60: The trade_date_for method currently falls back silently to
Date.current when parse_date(row.with_indifferent_access[:trade_date]) returns
nil; update trade_date_for to detect when parse_date returns nil and emit a
structured log (e.g., Rails.logger.warn or the model's logger) including the
original trade_date string and identifying row/context (such as account id,
symbol or full row hash) before returning Date.current so parsing failures are
observable; keep the existing return behavior but add the log in the branch
where parse_date is nil and reference the trade_date_for and parse_date methods
and supported_trade? validation flow.
In `@app/models/ibkr_account/historical_balances_sync.rb`:
- Around line 29-31: The comparison normalizes only the incoming row via the
local variable `currency` but not the stored value; change the guard to compare
normalized values (e.g., compare `currency` to `ibkr_account.currency` after
normalizing the latter) so both sides use the same case/format and you don't
drop valid rows — use `ibkr_account.currency.to_s.upcase` (or `&.upcase`) when
comparing to `currency`.
In `@app/models/ibkr_account/holdings_processor.rb`:
- Around line 40-50: process_group currently takes cost_basis from rows.first
which yields incorrect cost for merged lots; instead compute a weighted-average
per-share cost across rows by summing each row's
(parse_decimal(row[:cost_basis_price]) * parse_decimal(row[:position])) and
dividing by total quantity (rows.sum of parse_decimal(row[:position]) with
BigDecimal("0") fallback), skipping rows with missing values as appropriate,
then use that per-share cost_basis when creating the Holding; update references
in process_group to use this computed weighted per-share cost (and ensure
Holding stores cost_basis as per-share value).
In `@app/models/ibkr_account/processor.rb`:
- Around line 11-15: The call to repair_default_opening_anchor! runs too early
and must be moved after the activity sync so non-valuation entries exist; update
the sequence in the processor to call update_account_balance!, run
IbkrAccount::HoldingsProcessor.new(ibkr_account).process and
IbkrAccount::ActivitiesProcessor.new(ibkr_account).process first, then invoke
repair_default_opening_anchor! so the method (repair_default_opening_anchor!)
can detect the created non-valuation entries produced by
IbkrAccount::ActivitiesProcessor and perform the first-sync correction
correctly.
In `@app/models/ibkr_item.rb`:
- Line 22: The scope `syncable` is filtering `token` with a value-based check
(`where.not(token: [ nil, "" ])`) which fails for non-deterministically
encrypted `token`; change the filter to avoid comparing plaintext values — e.g.
replace the token predicate with a NULL-only check (`where.not(token: nil)`) or
maintain a separate boolean/metadata column for token presence, while leaving
the `query_id` check as-is; update the `scope :syncable` definition accordingly
(reference: scope :syncable, token, query_id).
In `@app/models/ibkr_item/syncer.rb`:
- Around line 12-16: The code currently raises a plain StandardError after
calling collect_health_stats which causes the generic rescue to double-report
the same issue as a sync_error; change the behavior so missing credentials are
signaled with a specific exception (e.g., define IBKRCredentialsMissingError or
IBKR::CredentialsMissing) or simply return early instead of raising
StandardError in the credential-check branches (the block around
ibkr_item.credentials_configured? and the similar block at lines ~52-58); also
add an explicit rescue for that new credentials exception (or rely on the early
return) so the existing generic rescue does not record a duplicate sync_error
for auth problems.
In `@app/models/trade.rb`:
- Around line 21-34: The exchange_rate= setter currently accepts non-positive
numbers; update it so after converting value to Float in exchange_rate= (method)
you check that normalized_value > 0 and treat non-positive values as invalid: if
normalized_value is not > 0 raise an ArgumentError (or otherwise jump to the
rescue path) so the subsequent self.extra merge stores either a positive numeric
exchange_rate with "exchange_rate_invalid" => false or stores the original value
with "exchange_rate_invalid" => true; reference the exchange_rate= method,
normalized_value, and the extra / "exchange_rate_invalid" merge when making the
change.
In `@app/models/transaction.rb`:
- Around line 159-168: activity_security currently memoizes `@activity_security`
and can return stale objects if activity_security_id (derived from
extra["security_id"]) changes; modify activity_security to track the id used for
the cache (e.g. store a cached_id alongside `@activity_security`) and only return
the memoized object if cached_id == activity_security_id, otherwise reload with
Security.find_by and update cached_id; also update
set_preloaded_activity_security(security) to set both `@activity_security` and the
cached_id to security&.id so preloaded values stay in sync.
In `@app/views/ibkr_items/_ibkr_item.html.erb`:
- Around line 19-107: Replace all hard-coded user-facing strings in this partial
with calls to the t(...) i18n helper and move keys into a hierarchical namespace
(e.g., activerecord/views.ibkr_items or views.ibkr_items) so they can be
localized; specifically swap the visible texts "Deletion in progress", "Flex Web
Service", "Syncing", "Credentials need attention", "Error", "Synced ... ago.
...", "Never synced.", menu/link labels "Set up accounts", "Delete", empty-state
headings "Accounts need setup", "No IBKR accounts discovered yet.", and
explanatory lines into translation lookups, and pass translated strings into
components/constructors used here (DS::Tooltip, DS::Menu items, DS::Link, and
CustomConfirm.for_resource_deletion) and into ProviderSyncSummary render calls
as needed so all displayed copy uses t(...) keys.
In `@app/views/ibkr_items/setup_accounts.html.erb`:
- Line 146: Replace the slash-opacity token classes in the markup: change
"border-success/20" to the supported equivalents (e.g. "border-success
border-opacity-20") and change "bg-success/5" to "bg-success bg-opacity-5" (or
other non-slash token variants your design system supports) in the element that
currently contains those classes so the utility classes generate CSS correctly.
In `@app/views/settings/providers/_ibkr_panel.html.erb`:
- Around line 10-120: The partial contains many hard-coded user-facing strings
(setup steps, list items, headings, placeholders, button labels like "Sync" and
"Update Configuration", status text from ibkr_item.sync_status_summary, and the
"Disconnect Interactive Brokers?" confirm) that must be replaced with i18n
lookups; update this view to call t(...) for each visible string (e.g.,
headings, each li text, form.label/placeholder values for :query_id and :token,
button labels used in button_to and form.submit, the turbo_confirm value, the
"Not configured." and the explanatory paragraph) and then add corresponding keys
under config/locales/en.yml (namespace them under something like ibkr_panel.* to
keep them discoverable). Ensure dynamic text like ibkr_item.sync_status_summary
remains unchanged but any surrounding/static fragments use t() and include
interpolation keys where needed.
In `@app/views/settings/providers/show.html.erb`:
- Line 82: Replace the hard-coded section title string with the i18n helper and
add the corresponding locale key; in the ERB view change the settings_section
call to use t('settings.providers.ibkr.title') instead of the literal
"Interactive Brokers Flex Query (beta)" and add a new entry under en -> settings
-> providers -> ibkr -> title in config/locales/en.yml with the same text.
In `@db/migrate/20260510140000_create_ibkr_items_and_accounts.rb`:
- Around line 6-9: The migration creates defaulted columns that remain
nullable—change the column definitions for status, scheduled_for_deletion,
pending_account_setup, and raw_payload to include null: false so defaults cannot
be overridden with NULL, and apply the same null: false enforcement to the other
defaulted state/payload columns referenced around lines 26–29 in this migration;
update their t.string/t.boolean/t.jsonb declarations to add null: false to
ensure DB-level non-null guarantees.
In `@flake.nix`:
- Around line 120-130: The dev Redis instance is using the default TCP endpoint
and may collide with a system Redis; update redis_start and redis_stop to use a
repo-local Unix socket under REDIS_DIR (e.g., "$REDIS_DIR/redis.sock") so the
instance is isolated. In redis_start (function redis_start) add the redis-server
option to create the Unix socket (--unixsocket "$REDIS_DIR/redis.sock") and keep
pidfile/log/dir as before; in redis_stop (function redis_stop) invoke redis-cli
with the socket option (e.g., redis-cli -s "$REDIS_DIR/redis.sock" shutdown
nosave ...) so it only shuts down the repo-local server, and ensure any
references to host/port are removed or replaced with the socket path.
---
Outside diff comments:
In `@app/models/holding/materializer.rb`:
- Around line 145-154: The
cleanup_stale_calculated_rows_on_latest_provider_snapshot method currently
deletes all calculated holdings for a provider_snapshot_date, which risks
removing valid calculated rows when provider snapshots are partial; either add
tests that validate provider snapshot completeness for
account.latest_provider_holdings_snapshot_date and assert that deleting all
account.holdings.where(account_provider_id: nil, date: provider_snapshot_date)
is safe, or refactor this method to follow the targeted approach used in
cleanup_shadowed_calculated_holdings: construct a subquery/join between
account.holdings (where account_provider_id is nil) and provider holdings for
the same account and provider_snapshot_date and delete only calculated rows that
match on security_id, date and currency (and account/provider identifiers)
instead of deleting by date alone.
---
Nitpick comments:
In `@app/models/holding.rb`:
- Around line 259-265: The rescue in amount_in_account_currency currently
swallows Money::ConversionError; update it to log the conversion failure
(including source currency, target account.currency, date, and the error
message/exception) before returning amount so missing exchange-rate incidents
are observable; reference the amount_in_account_currency method and the
Money::ConversionError rescue block and use the model logger (e.g., Rails.logger
or logger) to emit a warn or error-level message with enough context to trace
the failed conversion.
In `@app/models/ibkr_item/unlinking.rb`:
- Line 24: The current unlink loop uses links.each(&:destroy!) which deletes
each Link record individually and can be slow; if AccountProvider#destroy
callbacks are not required during unlink, replace the per-record destroy with a
batched deletion (e.g., use destroy_all or delete_all on the links relation) to
improve performance; locate the call to links.each(&:destroy!) in unlinking.rb
(the unlink flow that pertains to AccountProvider) and switch to
links.destroy_all (or links.delete_all if callbacks and validations must be
skipped) ensuring you pick destroy_all only if callbacks are safe to omit and
AccountProvider#destroy is not needed.
In `@app/models/provider/ibkr_flex.rb`:
- Around line 19-21: Replace hard-coded Flex endpoint and timeout with
env-backed config: read endpoint from an environment variable (e.g.
ENV['IBKR_FLEX_ENDPOINT'] with a sensible default like the current URL) and read
timeout from an environment variable (e.g. ENV['IBKR_FLEX_TIMEOUT'] parsed to
integer with default 120). Use those variables when calling base_uri and when
merging into default_options (the existing default_options.merge! call) while
keeping the existing headers and httparty_ssl_options. Update any relevant
initialization or docs to note the new ENV keys.
In `@app/views/ibkr_items/_ibkr_item.html.erb`:
- Around line 11-13: Replace the raw hex Tailwind classes on the IB badge with
design-system token classes: swap the div's bg-[`#D32F2F`]/10 for the appropriate
background token (e.g., bg-destructive-10 or the project's equivalent token) and
replace the span's text-[`#D32F2F`] with the matching text token (e.g.,
text-destructive-600 or the project's equivalent); update the element with those
token class names (the div with the rounded bg and the span with "IB") so the
component uses the shared Tailwind design tokens instead of hard-coded hex
values.
In `@app/views/ibkr_items/select_existing_account.html.erb`:
- Line 24: The view is formatting currency inline (using number_to_currency and
Money::Currency.new on ibkr_account.current_balance); move this logic to the
server by adding a formatted_balance attribute for each account in the
controller (e.g., when preparing `@available_ibkr_accounts`) or by creating a
helper method (e.g., format_currency_balance(account) used from the controller)
that computes number_to_currency(acct.current_balance || 0, unit:
Money::Currency.new(acct.currency || "USD").symbol) and pass that
formatted_balance to the view so the template only renders
ibkr_account.formatted_balance.
In `@app/views/settings/providers/_ibkr_panel.html.erb`:
- Around line 11-53: The ordered list (<ol>) in _ibkr_panel.html.erb has
multiple <ul> elements as direct children which breaks semantic nesting; move
each <ul> so it is nested inside the preceding related <li> (e.g., wrap the
"Cash Report", "Cash Transactions", "Net Asset Value", "Open Positions",
"Trades" and the "Set the following configuration options" lists inside their
parent <li> elements) so every <ul> is a child of its corresponding <li> and the
<ol> contains only <li> children.
In `@test/models/snaptrade_account_processor_test.rb`:
- Line 134: Remove the fragile stub of
Account.any_instance.stubs(:set_current_balance) so the test exercises the real
Account#set_current_balance behavior and only asserts observable outputs
(balance, cash_balance, currency); delete that stub line and, if the real method
requires setup, initialize the Account fixture or its dependencies (e.g.,
balances, transactions) instead or, if necessary, stub a lower-level external
dependency used by Account#set_current_balance rather than stubbing the method
under test.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: dc1fac98-33d2-4b3e-ab9c-14eacdb199b8
⛔ Files ignored due to path filters (1)
flake.lockis excluded by!**/*.lock
📒 Files selected for processing (72)
app/components/UI/account/activity_date.html.erbapp/controllers/accounts_controller.rbapp/controllers/ibkr_items_controller.rbapp/controllers/transactions_controller.rbapp/javascript/controllers/time_series_chart_controller.jsapp/models/account.rbapp/models/account/provider_import_adapter.rbapp/models/account/syncer.rbapp/models/balance/sync_cache.rbapp/models/data_enrichment.rbapp/models/family.rbapp/models/family/ibkr_connectable.rbapp/models/holding.rbapp/models/holding/materializer.rbapp/models/holding/portfolio_cache.rbapp/models/ibkr_account.rbapp/models/ibkr_account/activities_processor.rbapp/models/ibkr_account/data_helpers.rbapp/models/ibkr_account/historical_balances_sync.rbapp/models/ibkr_account/holdings_processor.rbapp/models/ibkr_account/processor.rbapp/models/ibkr_item.rbapp/models/ibkr_item/importer.rbapp/models/ibkr_item/provided.rbapp/models/ibkr_item/report_parser.rbapp/models/ibkr_item/syncer.rbapp/models/ibkr_item/unlinking.rbapp/models/provider/ibkr_adapter.rbapp/models/provider/ibkr_flex.rbapp/models/provider_connection_status.rbapp/models/snaptrade_account/processor.rbapp/models/trade.rbapp/models/transaction.rbapp/models/transaction/activity_security_preloader.rbapp/views/accounts/index.html.erbapp/views/holdings/_cash.html.erbapp/views/holdings/_cost_basis_cell.html.erbapp/views/holdings/_holding.html.erbapp/views/ibkr_items/_ibkr_item.html.erbapp/views/ibkr_items/select_existing_account.html.erbapp/views/ibkr_items/setup_accounts.html.erbapp/views/settings/providers/_ibkr_panel.html.erbapp/views/settings/providers/show.html.erbapp/views/trades/_trade.html.erbapp/views/transactions/_transaction.html.erbconfig/locales/views/ibkr_items/en.ymlconfig/routes.rbdb/migrate/20260510140000_create_ibkr_items_and_accounts.rbdb/migrate/20260510150000_add_extra_to_trades.rbdb/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rbdb/schema.rbflake.nixtest/controllers/ibkr_items_controller_test.rbtest/fixtures/files/ibkr/flex_statement.xmltest/fixtures/ibkr_accounts.ymltest/fixtures/ibkr_items.ymltest/models/account/provider_import_adapter_test.rbtest/models/account/syncer_test.rbtest/models/account_ibkr_creation_test.rbtest/models/account_test.rbtest/models/balance/sync_cache_test.rbtest/models/holding/materializer_test.rbtest/models/holding/portfolio_cache_test.rbtest/models/holding_test.rbtest/models/ibkr_account/historical_balances_sync_test.rbtest/models/ibkr_account_processor_test.rbtest/models/ibkr_item_importer_test.rbtest/models/ibkr_item_report_parser_test.rbtest/models/snaptrade_account_processor_test.rbtest/models/trade_test.rbtest/models/transaction/activity_security_preloader_test.rbtest/models/transaction_test.rb
|
I'll address the additional comments tomorrow! |
5b37aeb to
7732084
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
app/models/ibkr_account/processor.rb (1)
12-14:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMove opening-anchor repair after activities import
On Line 12,
repair_default_opening_anchor!runs beforeIbkrAccount::ActivitiesProcessor. But the repair guard on Line 44 requires existing non-valuation entries, so first sync can skip the repair path.Suggested minimal reorder
def process return unless ibkr_account.current_account.present? update_account_balance! - repair_default_opening_anchor! IbkrAccount::HoldingsProcessor.new(ibkr_account).process IbkrAccount::ActivitiesProcessor.new(ibkr_account).process + repair_default_opening_anchor! ibkr_account.current_account.broadcast_sync_complete endAlso applies to: 44-44
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/models/ibkr_account/processor.rb` around lines 12 - 14, The call to repair_default_opening_anchor! currently runs before IbkrAccount::ActivitiesProcessor and can be skipped due to the repair guard that requires non-valuation entries; move the repair_default_opening_anchor! invocation to run after IbkrAccount::ActivitiesProcessor.new(ibkr_account).process (i.e., reorder so HoldingsProcessor.process, ActivitiesProcessor.process, then repair_default_opening_anchor!) to ensure the guard sees imported activity entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@app/models/ibkr_account/processor.rb`:
- Around line 12-14: The call to repair_default_opening_anchor! currently runs
before IbkrAccount::ActivitiesProcessor and can be skipped due to the repair
guard that requires non-valuation entries; move the
repair_default_opening_anchor! invocation to run after
IbkrAccount::ActivitiesProcessor.new(ibkr_account).process (i.e., reorder so
HoldingsProcessor.process, ActivitiesProcessor.process, then
repair_default_opening_anchor!) to ensure the guard sees imported activity
entries.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f17d2b03-e321-478b-afd9-24a17420967e
📒 Files selected for processing (64)
app/components/UI/account/activity_date.html.erbapp/controllers/accounts_controller.rbapp/controllers/ibkr_items_controller.rbapp/controllers/transactions_controller.rbapp/javascript/controllers/time_series_chart_controller.jsapp/models/account.rbapp/models/account/provider_import_adapter.rbapp/models/account/syncer.rbapp/models/balance/sync_cache.rbapp/models/data_enrichment.rbapp/models/family.rbapp/models/family/ibkr_connectable.rbapp/models/holding/portfolio_cache.rbapp/models/ibkr_account.rbapp/models/ibkr_account/activities_processor.rbapp/models/ibkr_account/data_helpers.rbapp/models/ibkr_account/historical_balances_sync.rbapp/models/ibkr_account/holdings_processor.rbapp/models/ibkr_account/processor.rbapp/models/ibkr_item.rbapp/models/ibkr_item/importer.rbapp/models/ibkr_item/provided.rbapp/models/ibkr_item/report_parser.rbapp/models/ibkr_item/syncer.rbapp/models/ibkr_item/unlinking.rbapp/models/provider/ibkr_adapter.rbapp/models/provider/ibkr_flex.rbapp/models/provider_connection_status.rbapp/models/trade.rbapp/models/transaction.rbapp/models/transaction/activity_security_preloader.rbapp/views/accounts/index.html.erbapp/views/holdings/_cash.html.erbapp/views/holdings/_cost_basis_cell.html.erbapp/views/holdings/_holding.html.erbapp/views/ibkr_items/_ibkr_item.html.erbapp/views/ibkr_items/select_existing_account.html.erbapp/views/ibkr_items/setup_accounts.html.erbapp/views/settings/providers/_ibkr_panel.html.erbapp/views/settings/providers/show.html.erbapp/views/trades/_trade.html.erbapp/views/transactions/_transaction.html.erbconfig/locales/views/ibkr_items/en.ymlconfig/routes.rbdb/migrate/20260510140000_create_ibkr_items_and_accounts.rbdb/migrate/20260510150000_add_extra_to_trades.rbdb/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rbdb/schema.rbtest/controllers/ibkr_items_controller_test.rbtest/fixtures/files/ibkr/flex_statement.xmltest/fixtures/ibkr_accounts.ymltest/fixtures/ibkr_items.ymltest/models/account/provider_import_adapter_test.rbtest/models/account/syncer_test.rbtest/models/account_ibkr_creation_test.rbtest/models/balance/sync_cache_test.rbtest/models/holding/portfolio_cache_test.rbtest/models/ibkr_account/historical_balances_sync_test.rbtest/models/ibkr_account_processor_test.rbtest/models/ibkr_item_importer_test.rbtest/models/ibkr_item_report_parser_test.rbtest/models/trade_test.rbtest/models/transaction/activity_security_preloader_test.rbtest/models/transaction_test.rb
✅ Files skipped from review due to trivial changes (12)
- app/views/holdings/_cash.html.erb
- app/views/holdings/_cost_basis_cell.html.erb
- test/fixtures/ibkr_items.yml
- app/javascript/controllers/time_series_chart_controller.js
- app/components/UI/account/activity_date.html.erb
- test/fixtures/files/ibkr/flex_statement.xml
- app/views/holdings/_holding.html.erb
- test/fixtures/ibkr_accounts.yml
- test/models/ibkr_item_report_parser_test.rb
- test/controllers/ibkr_items_controller_test.rb
- config/locales/views/ibkr_items/en.yml
- app/models/family/ibkr_connectable.rb
🚧 Files skipped from review as they are similar to previous changes (50)
- app/models/family.rb
- app/controllers/transactions_controller.rb
- app/models/ibkr_item/importer.rb
- app/models/data_enrichment.rb
- test/models/transaction/activity_security_preloader_test.rb
- app/models/balance/sync_cache.rb
- app/models/ibkr_item/provided.rb
- test/models/account/syncer_test.rb
- app/models/ibkr_item/unlinking.rb
- app/views/accounts/index.html.erb
- app/models/provider_connection_status.rb
- db/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rb
- app/models/trade.rb
- app/views/ibkr_items/_ibkr_item.html.erb
- test/models/ibkr_account/historical_balances_sync_test.rb
- app/views/trades/_trade.html.erb
- app/views/settings/providers/show.html.erb
- config/routes.rb
- test/models/ibkr_item_importer_test.rb
- app/models/transaction.rb
- app/models/account.rb
- test/models/ibkr_account_processor_test.rb
- app/views/ibkr_items/select_existing_account.html.erb
- test/models/account/provider_import_adapter_test.rb
- test/models/account_ibkr_creation_test.rb
- app/views/transactions/_transaction.html.erb
- app/models/provider/ibkr_adapter.rb
- app/models/holding/portfolio_cache.rb
- db/migrate/20260510150000_add_extra_to_trades.rb
- app/models/ibkr_account.rb
- test/models/balance/sync_cache_test.rb
- app/models/ibkr_account/data_helpers.rb
- app/controllers/accounts_controller.rb
- app/models/transaction/activity_security_preloader.rb
- app/models/ibkr_account/holdings_processor.rb
- test/models/holding/portfolio_cache_test.rb
- test/models/trade_test.rb
- app/models/ibkr_account/historical_balances_sync.rb
- app/views/settings/providers/_ibkr_panel.html.erb
- app/views/ibkr_items/setup_accounts.html.erb
- test/models/transaction_test.rb
- app/models/ibkr_item.rb
- app/models/ibkr_item/report_parser.rb
- app/models/account/provider_import_adapter.rb
- app/models/provider/ibkr_flex.rb
- db/migrate/20260510140000_create_ibkr_items_and_accounts.rb
- app/models/ibkr_account/activities_processor.rb
- app/models/ibkr_item/syncer.rb
- app/controllers/ibkr_items_controller.rb
- db/schema.rb
|
Great to see the IBKR integration land — the architecture follows the established provider pattern (IbkrItem / IbkrAccount mirroring SnapTrade, Coinbase, etc.) which makes it easy to follow. The decomposition into separate processors ( IBKR uses Flex Query Reports (XML pull) rather than a push/REST API
Generated by Claude Code |
c1286b8 to
03e3f6d
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/models/account/opening_balance_manager.rb`:
- Around line 54-60: The memoized `@oldest_entry_date` uses ||= which caches a
nil/old value and causes stale validation; change it to compute freshest value
on each call by removing the ||= memoization and returning the computed date
directly (i.e., always evaluate the block that checks
opening_anchor_valuation&.entry and calls
account.entries.where.not(...).minimum(:date) or
account.entries.minimum(:date)); update the method/attribute accessor that
references `@oldest_entry_date` so it no longer relies on cached state (or
explicitly clear `@oldest_entry_date` before use) to ensure set_opening_balance
validates against current data.
In `@app/models/ibkr_account/historical_balances_sync.rb`:
- Around line 27-29: The code calls row.with_indifferent_access inside the
filter_map assuming every payload row is a Hash; add a type guard to skip
malformed rows before calling with_indifferent_access (e.g., return nil/next for
nil or non-Hash rows) so the filter_map block only processes valid hashes;
update the block around filter_map/with_indifferent_access to check
row.is_a?(Hash) or respond_to?(:to_hash) and then call with_indifferent_access
and continue using currency = data[:currency].presence&.upcase.
In `@test/models/ibkr_item/syncer_test.rb`:
- Around line 6-16: The test currently constructs IbkrItem::Syncer in setup,
which can capture credentials at initialization and mask the missing-token path;
modify the test "perform_sync records a single auth error when credentials are
missing" to build the syncer after clearing credentials by moving the
instantiation of IbkrItem::Syncer (currently assigned to `@syncer` in setup) into
the test body after `@ibkr_item.update`!(token: nil) (e.g., syncer =
IbkrItem::Syncer.new(`@ibkr_item`)), then call syncer.perform_sync(sync) so
perform_sync executes with the missing-token state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d49f544d-2c38-4b74-bd30-3e3e9821bb5e
⛔ Files ignored due to path filters (1)
flake.lockis excluded by!**/*.lock
📒 Files selected for processing (77)
app/components/UI/account/activity_date.html.erbapp/controllers/accounts_controller.rbapp/controllers/ibkr_items_controller.rbapp/controllers/transactions_controller.rbapp/javascript/controllers/time_series_chart_controller.jsapp/models/account.rbapp/models/account/opening_balance_manager.rbapp/models/account/provider_import_adapter.rbapp/models/account/syncer.rbapp/models/balance/sync_cache.rbapp/models/data_enrichment.rbapp/models/family.rbapp/models/family/ibkr_connectable.rbapp/models/holding.rbapp/models/holding/materializer.rbapp/models/holding/portfolio_cache.rbapp/models/ibkr_account.rbapp/models/ibkr_account/activities_processor.rbapp/models/ibkr_account/data_helpers.rbapp/models/ibkr_account/historical_balances_sync.rbapp/models/ibkr_account/holdings_processor.rbapp/models/ibkr_account/processor.rbapp/models/ibkr_item.rbapp/models/ibkr_item/importer.rbapp/models/ibkr_item/provided.rbapp/models/ibkr_item/report_parser.rbapp/models/ibkr_item/syncer.rbapp/models/ibkr_item/unlinking.rbapp/models/provider/ibkr_adapter.rbapp/models/provider/ibkr_flex.rbapp/models/provider_connection_status.rbapp/models/snaptrade_account/processor.rbapp/models/trade.rbapp/models/transaction.rbapp/models/transaction/activity_security_preloader.rbapp/views/accounts/index.html.erbapp/views/holdings/_cash.html.erbapp/views/holdings/_cost_basis_cell.html.erbapp/views/holdings/_holding.html.erbapp/views/ibkr_items/_ibkr_item.html.erbapp/views/ibkr_items/select_existing_account.html.erbapp/views/ibkr_items/setup_accounts.html.erbapp/views/settings/providers/_ibkr_panel.html.erbapp/views/settings/providers/show.html.erbapp/views/trades/_trade.html.erbapp/views/transactions/_transaction.html.erbconfig/locales/views/ibkr_items/en.ymlconfig/locales/views/settings/en.ymlconfig/routes.rbdb/migrate/20260510140000_create_ibkr_items_and_accounts.rbdb/migrate/20260510150000_add_extra_to_trades.rbdb/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rbdb/schema.rbflake.nixtest/controllers/ibkr_items_controller_test.rbtest/fixtures/files/ibkr/flex_statement.xmltest/fixtures/ibkr_accounts.ymltest/fixtures/ibkr_items.ymltest/models/account/opening_balance_manager_test.rbtest/models/account/provider_import_adapter_test.rbtest/models/account/syncer_test.rbtest/models/account_ibkr_creation_test.rbtest/models/account_test.rbtest/models/balance/sync_cache_test.rbtest/models/holding/materializer_test.rbtest/models/holding/portfolio_cache_test.rbtest/models/holding_test.rbtest/models/ibkr_account/historical_balances_sync_test.rbtest/models/ibkr_account_processor_test.rbtest/models/ibkr_item/syncer_test.rbtest/models/ibkr_item_importer_test.rbtest/models/ibkr_item_report_parser_test.rbtest/models/ibkr_item_test.rbtest/models/snaptrade_account_processor_test.rbtest/models/trade_test.rbtest/models/transaction/activity_security_preloader_test.rbtest/models/transaction_test.rb
✅ Files skipped from review due to trivial changes (12)
- app/views/holdings/_cash.html.erb
- test/fixtures/files/ibkr/flex_statement.xml
- app/javascript/controllers/time_series_chart_controller.js
- app/components/UI/account/activity_date.html.erb
- config/locales/views/settings/en.yml
- test/fixtures/ibkr_items.yml
- app/views/holdings/_holding.html.erb
- test/models/transaction_test.rb
- test/fixtures/ibkr_accounts.yml
- test/models/trade_test.rb
- config/locales/views/ibkr_items/en.yml
- test/models/transaction/activity_security_preloader_test.rb
🚧 Files skipped from review as they are similar to previous changes (57)
- db/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rb
- app/models/data_enrichment.rb
- app/models/family.rb
- app/views/settings/providers/show.html.erb
- app/models/balance/sync_cache.rb
- app/models/provider_connection_status.rb
- app/views/trades/_trade.html.erb
- app/models/holding.rb
- app/models/account/syncer.rb
- app/controllers/accounts_controller.rb
- app/views/ibkr_items/select_existing_account.html.erb
- app/controllers/transactions_controller.rb
- app/views/accounts/index.html.erb
- test/models/holding_test.rb
- config/routes.rb
- test/models/account/syncer_test.rb
- test/models/ibkr_item_importer_test.rb
- test/models/holding/portfolio_cache_test.rb
- test/models/account_test.rb
- app/views/holdings/_cost_basis_cell.html.erb
- app/models/family/ibkr_connectable.rb
- app/models/ibkr_item/provided.rb
- test/models/account/provider_import_adapter_test.rb
- app/models/ibkr_item/importer.rb
- app/models/holding/portfolio_cache.rb
- app/views/ibkr_items/_ibkr_item.html.erb
- app/models/provider/ibkr_adapter.rb
- app/models/account/provider_import_adapter.rb
- test/models/balance/sync_cache_test.rb
- app/models/holding/materializer.rb
- test/models/snaptrade_account_processor_test.rb
- test/models/ibkr_item_report_parser_test.rb
- app/models/snaptrade_account/processor.rb
- test/models/account_ibkr_creation_test.rb
- app/models/transaction.rb
- app/models/trade.rb
- app/models/ibkr_account/processor.rb
- app/models/transaction/activity_security_preloader.rb
- app/views/transactions/_transaction.html.erb
- db/migrate/20260510150000_add_extra_to_trades.rb
- db/migrate/20260510140000_create_ibkr_items_and_accounts.rb
- app/models/ibkr_item/unlinking.rb
- app/views/settings/providers/_ibkr_panel.html.erb
- app/models/account.rb
- app/models/ibkr_item/report_parser.rb
- app/views/ibkr_items/setup_accounts.html.erb
- flake.nix
- test/models/holding/materializer_test.rb
- app/models/ibkr_account.rb
- app/models/ibkr_account/data_helpers.rb
- test/controllers/ibkr_items_controller_test.rb
- app/models/ibkr_item.rb
- db/schema.rb
- app/models/provider/ibkr_flex.rb
- app/controllers/ibkr_items_controller.rb
- app/models/ibkr_account/activities_processor.rb
- app/models/ibkr_item/syncer.rb
Thanks for the review! I looked through the points and here are my thoughts: Flex / polling model: You're right that the API of IBKR Flex Queries is quite different from the other providers, but to my knowledge, Flex Queries are the only way for individuals to access their IBKR data easily via simple HTTP calls, and without having to set up additional tools (like a separate IB Gateway, for example). Unfortunately, the API requires one call to kick off the report generation, and another one to obtain the report (and we don't know when it's ready!), which is why polling is needed. In the current flow, both Controller size: fair note. After comparing it with the other provider controllers in Sure, it still feels within the normal range for controllers that own account setup / linking flows. So if it's fine for you, I'd keep this as-is for now as well. XML Report parser robustness: I agree with the comment and improved the error handling clarity in an additional commit. The parser now raises a scoped Activity codes: I took another look and I don't think we're really dispatching on opaque IBKR codes so much as on the human-readable string values that appear in Flex exports (DEPOSITS/WITHDRAWALS, DIVIDENDS, BUY, SELL). I'm also not aware of a useful upstream reference that enumerates these cleanly, so I'd prefer not to add a documentation comment that implies stronger official IBKR coverage than actually exists. The documentation of the Flex Query API is quite sparse, so the only way to get a comprehensive list of activity types is to look at an actual export (which is what I used to implement this)... TL;DR: I think the current implementation is a reasonable balance between robustness and complexity, so I only addressed the XML parsing error handling in a more explicit way. Let me know if that is sufficient from your perspective! |
|
@jjmata Uff, I saw you already merged the bank sync cleanup 😅 I'm rebasing now, and will update everything such that the provider is integrated into the new structure. |
52c22dd to
369d7da
Compare
|
@jjmata Alright, I rebased to the latest Lastly, I did another smoke-test to confirm that everything works as expected, and I wasn't able to find any issues. So IMO this is ready to merge, thanks for the patience! |
|
Solid integration — the XML parser, multi-currency trade handling via 1. The old implementation only deleted calculated holdings whose securities were not in the provider snapshot (scoped by deleted_count = account.holdings
.where(account_provider_id: nil, date: provider_snapshot_date)
.delete_allFor a pure-IBKR account this is fine. But for an account where a user has also entered manual holdings on the same date as a provider snapshot, those manual rows (also 2. IBKR cash transaction FX rates stored in Trades get a first-class 3. begin
@ibkr_item.unlink_all!(dry_run: false)
rescue => e
Rails.logger.warn("IBKR unlink during destroy failed: ...")
end
@ibkr_item.destroy_laterIf 4. Nit: redundant
Generated by Claude Code |
Yeah, sorry ... things move at a different pace these days. Are you in the Discord? Tried to give a heads up in some PRs (BREX) but forgot about this one I guess. 🙄 This month is shaping up to be "sync improvements" release month! |
369d7da to
8b41831
Compare
Thanks for the review! I went through each point in detail and addressed the first and last ones. The fixes are already fixupped and pushed.
|
No worries, it was actually more straightforward than I expected! I somehow didn't see that there is a Discord, but I joined today. |
|
@jjmata I addressed your points above and pushed the changes, so this should be ready! |
jjmata
left a comment
There was a problem hiding this comment.
I'm going to stop reviewing, as I see a bunch of non-IBKR related changes? Did you merge another branch/PR in to your tree for any reason?
Sorry for the confusion, I should have carried over more of the context from my original PR here as well. No, I did not merge another branch/PR into this one. In addition to the core IBKR provider work, I also made a few follow-up changes that came up during smoke-testing and review. In particular, I wanted investment activity in the history views to display the related security logo instead of only a generic colored circle/letter, and I fixed a couple of UI issues I noticed there at the same time. Most of the controller/model changes you pointed out below are related to that follow-up commit ("Add logos in activity history"). In the original PR, CodeRabbit also flagged that this introduced an N+1 lookup path for resolving the related security on transaction rows, see: #1712 (comment) That is why I added The |
|
OK, that helps explain things! So much activity that I missed the comments from the old PR. Can you resolve the merge conflicts and remaining PR review comments here? |
No worries! I will rebase this evening if that's fine for you, as I don't have enough time right now. Is there anything else you would like me to change? One thing I mentioned in my original PR was that I added a Nix devshell, so I can more easily work in this repo on NixOS (as an alternative to the devcontainer). However, this commit ("Add nix devshell") is not related to the IBKR provider and the other improvements, so if you want I can remove it if you don't want to include a |
|
No rush! I'm trying to work through the backlog or PRs, so the cleanest (and smallest) ones are the easier ones to merge.
No, that's great ... but please separate it so others can see it / comment on it and it gets release notes treatment! Otherwise it goes unnoticed. :-) |
8b41831 to
c78cf79
Compare
|
Thanks for the quick progress here. I see at least 2 or 3 other thread that need to go into separate PRs:
Anything else, @gian-reto? |
Working on it! The exchange rate holding fixes kind of need to be in this PR, as the IBKR provider would be broken without it... I'm currently removing the Nix stuff. As for the privacy mode additions, they're technically not necessary for this PR, but it's a super small change limited to the daily totals only, so I'd also prefer to keep it here. I'll probably do some more privacy-related changes, but I'll put them in a separate PR. Would you be ok with it if I remove only the Nix stuff and keep the other changes in this PR? |
c78cf79 to
805a02c
Compare
|
@jjmata alright, I rebased again, dropped the Nix stuff and pushed again. If you agree that we should keep the exchange rate and the few privacy mode changes in (I would strongly suggest it, especially the exchange rate changes), this should be ready. |
805a02c to
762f6b7
Compare
|
Sure, you can leave those here, but can we find a better column name than Also, what are the SnapTrade changes related to? |
I mainly did it because I saw there was already Regardig SnapTrade: Only SnapTrade (in addition to now IBKR of course) needed the change because its processor was manually calculating |
Are both used for similar metadata? EDIT: nevermind, I see a lot of use of |
Unfortunately, my original PR (#1712) got closed, because GitHub rerouted my fork to point to
maybe-finance/maybewhen I pushed... No idea how that happened.Anyway, I had to make a new PR. I addressed all the items pointed out by Codex and CodeRabbit, and the tests should now pass.
If it looks good to you, feel free to merge @jjmata!
Summary by CodeRabbit
New Features
Improvements
Tests